package client;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.LinkedList;

import result.ClassRequest;
import result.Result;
import comm.ClientPriority;
import comm.Comm;
import comm.ConnectionType;
import comm.Streams;
import masterserver.MasterServer;
import tasks.Task;

public abstract class Client {

	private long startTime;
	private int masterPort;
	private String masterAddress;
	private Socket socket;
	private ObjectOutputStream out;
	private ObjectInputStream in;
	private ClientPriority priority;
	private int codeServerPort;
	private String codeServerAddress;
	private LinkedList<Result> results = new LinkedList<Result>();
	private int numberOfUnrespondedTasks = 0;

	private int problemNumber;
	private int version;

	public Client(ClientPriority priority, int problemNumber, int version,
			String masterAddress, int masterPort, String codeServerAddress,
			int codeServerPort) {

		this.priority = priority;
		this.problemNumber = problemNumber;
		this.version = version;
		this.masterAddress = masterAddress;
		this.codeServerAddress = codeServerAddress;

		startTime = System.currentTimeMillis();

		if (masterPort == 0) {
			this.masterPort = 10080;
		} else {
			this.masterPort = masterPort;
		}
		;

		if (codeServerPort == 0) {
			this.codeServerPort = 11080;
		} else {
			this.codeServerPort = codeServerPort;
		}
		;

		connectCodeServer();
		connectMaster();

	}

	private void connectCodeServer() {
		System.out.println("Trying to connect to CodeServer: "
				+ codeServerAddress + " on port " + this.codeServerPort);
		try {
			
			
			
			
			Streams InOut = new Comm(codeServerPort, codeServerAddress).startConnectionToServer();

			if(InOut!=null){
				ObjectInputStream inputStream = InOut.returnObjectInputStream();
				 ObjectOutputStream outputStream = InOut.returnObjectOutputStream();
				 

					outputStream.writeObject(ConnectionType.CLASS_SOLVER);
					outputStream.writeObject(new Integer(problemNumber));
					outputStream.writeObject(new Integer(version));

	
					new returnClassesThread(outputStream, inputStream).start();

					System.out.println("Connected to CodeServer");
			}
			else{
				System.out.println("Com has problems!!");
			}
			

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			try {
				in.close();
				out.close();
				socket.close();
				return;
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			
		}

	}

	protected void connectMaster() {

		System.out.println("Trying to connect to: " + masterAddress
				+ " on port " + this.masterPort);
		try {
			Streams InOut = new Comm(masterPort,masterAddress).startConnectionToServer();

			if(InOut!=null){
				in = InOut.returnObjectInputStream();
				out = InOut.returnObjectOutputStream();
				out.writeObject(ConnectionType.CLIENT);
				
				
				out.writeObject(ConnectionType.CLIENT_NEW_PROBLEM);
				out.writeObject(new Integer(problemNumber));
				out.writeObject(new Integer(version));
				out.writeObject(priority);
		
				new resultFetcher().start();
				System.out.println("Connected to MasterServer");
			}
			else{
				System.out.println("Comm has problems!!");
			}
			
			

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			try {
				in.close();
				out.close();
				socket.close();
				return;
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			
		}

	}

	public void commit(Task task) {
		try {
			System.out.println("Sending new Task...");
			out.writeObject(ConnectionType.CLIENT_NEW_TASK);
			out.writeObject(task);
			//return getResult();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//return null;
	}
	
	public void cancelAllTasks(){
		try {
			System.out.println("Canceling all Tasks...");
			out.writeObject(ConnectionType.CLIENT_CANCEL_ALL_TASKS);
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public void startWork(){
		try {
			out.writeObject(ConnectionType.START_WORK);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public Result getNextResult(){
		synchronized (results) {
			//	while(numberOfUnrespondedTasks > 0 && results.size() == 0){
			while(results.size() == 0){
				try {
					results.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println("Tenho nova task"+results.toString());
			return results.pollFirst();
		}
	}
	
	private Result getResult() {
		Result result = null;
		try {
			result = (Result) in.readObject();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return result ;
	}

	public abstract void execute();

	private class resultFetcher extends Thread {
		@Override
		public void run() {
			while(true){
				Result newResult = getResult();
				synchronized (results) {
					results.addLast(newResult);
					results.notify();
				}
			}
		}
	}
	
	private class returnClassesThread extends Thread {

		private String PATH_SEPARATOR = System.getProperty("file.separator");;
		private ObjectOutputStream out;
		private ObjectInputStream in;

		public returnClassesThread(ObjectOutputStream out, ObjectInputStream in) {
			super();
			this.out = out;
			this.in = in;
		}

		// Returns the contents of the file in a byte array.
		// from: http://www.exampledepot.com/egs/java.io/File2ByteArray.html
		public byte[] getBytesFromFile(File file) throws IOException {
		    InputStream is = new FileInputStream(file);

		    // Get the size of the file
		    long length = file.length();

		    // You cannot create an array using a long type.
		    // It needs to be an int type.
		    // Before converting to an int type, check
		    // to ensure that file is not larger than Integer.MAX_VALUE.
		    if (length > Integer.MAX_VALUE) {
		        // File is too large
		    }

		    // Create the byte array to hold the data
		    byte[] bytes = new byte[(int)length];

		    // Read in the bytes
		    int offset = 0;
		    int numRead = 0;
		    while (offset < bytes.length
		           && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
		        offset += numRead;
		    }

		    // Ensure all the bytes have been read in
		    if (offset < bytes.length) {
		        throw new IOException("Could not completely read file "+file.getName());
		    }

		    // Close the input stream and return bytes
		    is.close();
		    return bytes;
		}
		
		@Override
		public void run() {
			while (true) {
				try {
					Result result = (Result) in.readObject();
					if (result instanceof ClassRequest) {
						String className = ((ClassRequest) result).getName();
						System.out.println("Got request for: " + className);
//						Class<?> neededClass = Class.forName(className); // getClass().getClassLoader().loadClass(className);

						File file = new File("bin" + PATH_SEPARATOR
								+ className.replace(".", PATH_SEPARATOR)
								+ ".class");
						System.out.println(file.getAbsolutePath());
						byte classData[] = getBytesFromFile(file);
						System.out.println(classData.length);
						out.writeObject(classData);

						System.out.println("sent class " + className);
					}
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}

}
